package com.ruoyi.backend.util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.backend.config.MinioConfig;
import com.ruoyi.backend.vo.FileMergeReqVO;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;


@Component
public class MinioUtil {

    private static final Logger logger = LoggerFactory.getLogger(MinioUtil.class);

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioConfig minioConfig;

    // 线程池，用于异步备份
    private final ExecutorService backupExecutor = Executors.newFixedThreadPool(10);

    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 创建存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public boolean makeBucket(String bucketName) {

        try {

            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {

            e.printStackTrace();
        }
        return false;
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public boolean removeBucket(String bucketName) {

        try {

            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {

            e.printStackTrace();
        }
        return false;
    }

    /**
     * 查看存储bucket是否存在
     *
     * @param bucketName
     * @return
     */
    public boolean bucketExists(String bucketName) {

        try {
            return minioClient.bucketExists(BucketExistsArgs
                    .builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {

            e.printStackTrace();
        }
        return false;
    }

    /**
     * 文件上传到指定桶
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param file       文件
     * @return
     */
    public String uploadFile(String bucketName, String objectName, MultipartFile file) {
        try (InputStream stream = file.getInputStream()) {
            // 检查桶是否存在，不存在则返回错误
//            if (!bucketExists(bucketName)) {
//                throw new RuntimeException("Bucket 不存在: " + bucketName);
//            }

            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucketName)  // 使用传入的桶名称
                    .object(objectName)
                    .contentType(file.getContentType())
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .build();
            minioClient.putObject(putObjectArgs);

            return String.format("%s/%s/%s", minioConfig.getEndPoint(), bucketName, objectName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 获取指定桶的对象url
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return
     */
    public String getObjectUrl(String bucketName, String objectName) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)  // 使用传入的桶名称
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * 上传文件到 backup 桶（带时间目录，避免覆盖）
     *
     * @param file 文件
     * @return 备份文件 URL
     */
    public String uploadToBackup(MultipartFile file) {
        try {
            String fileName = file.getOriginalFilename();
            java.time.LocalDateTime now = java.time.LocalDateTime.now();

            // 月份目录，例如 202509
            String monthDir = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMM"));
            // 日目录，例如 11
            String dayDir = now.format(java.time.format.DateTimeFormatter.ofPattern("dd"));
            // 时间部分，例如 163045
            String timePart = now.format(java.time.format.DateTimeFormatter.ofPattern("HHmmss"));
            //保证不重复
            String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
            // 拼接对象名
            String backupObjectName = monthDir + "/" + dayDir + "/" + timePart + "_" + uuid + "_" + fileName;

            return uploadFile(minioConfig.getBackupBucket(), backupObjectName, file);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 上传文件到指定桶（带时间目录，避免覆盖）
     *
     * @param file 文件
     * @return 备份文件 URL
     */
    public String uploadToBucket(MultipartFile file, String bucketName) {
        try {
            String fileName = file.getOriginalFilename();
            java.time.LocalDateTime now = java.time.LocalDateTime.now();

            // 月份目录，例如 202509
            String monthDir = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMM"));
            // 日目录，例如 11
            String dayDir = now.format(java.time.format.DateTimeFormatter.ofPattern("dd"));
            // 时间部分，例如 163045
            String timePart = now.format(java.time.format.DateTimeFormatter.ofPattern("HHmmss"));
            //保证不重复
            String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
            // 拼接对象名
            String backupObjectName = monthDir + "/" + dayDir + "/" + timePart + "_" + uuid + "_" + fileName;

            return uploadFile(bucketName, backupObjectName, file);
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 删除指定桶中的文件
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 是否删除成功
     */
    public boolean removeFile(String bucketName, String objectName) {
        try {
            // 检查桶是否存在
            if (!bucketExists(bucketName)) {
                throw new RuntimeException("Bucket 不存在: " + bucketName);
            }

            // 检查文件是否存在
            if (!objectExist(bucketName, objectName)) {
                throw new RuntimeException("文件不存在: " + objectName);
            }

            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            throw new RuntimeException("删除文件失败: " + objectName, e);
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param bucketName 桶名称
     * @param objectName 文件名称
     * @return true 存在，false 不存在
     */
    public boolean objectExist(String bucketName, String objectName) {
        try {
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            return true;
        } catch (ErrorResponseException e) {
            // 仅当 MinIO 返回 NoSuchKey 时才认为文件不存在
            if ("NoSuchKey".equals(e.errorResponse().code())) {
                return false;
            }
            throw new RuntimeException("文件不存在: " + e.errorResponse().message(), e);
        } catch (Exception e) {
            throw new RuntimeException("检查文件时异常", e);
        }
    }


    /**
     * 生成随机文件名
     *
     * @param file 文件
     * @return backupObjectName
     */
    public String createFileName(MultipartFile file) {
        try {
            String fileName = file.getOriginalFilename();
            java.time.LocalDateTime now = java.time.LocalDateTime.now();

            // 月份目录，例如 202509
            String monthDir = now.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMM"));
            // 日目录，例如 11
            String dayDir = now.format(java.time.format.DateTimeFormatter.ofPattern("dd"));
            // 时间部分，例如 163045
            String timePart = now.format(java.time.format.DateTimeFormatter.ofPattern("HHmmss"));
            //保证不重复
            String uuid = java.util.UUID.randomUUID().toString().replace("-", "");

            // 拼接对象名

            return monthDir + "/" + dayDir + "/" + timePart + "_" + uuid + "_" + fileName;
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 分片上传 指定桶
     *
     * @return objectName
     */

    public String uploadFilePart(String bucketName, String fileId, String fileName,
                                 MultipartFile filePart, Integer chunkIndex,
                                 Integer totalChunks) throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        try (InputStream inputStream = filePart.getInputStream()) {
            String objectName = fileId + "/" + fileName + '-' + chunkIndex;

            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(inputStream, filePart.getSize(), -1)
                    .contentType(filePart.getContentType())
                    .build();

            minioClient.putObject(args);
            return objectName;
        }
    }

    /**
     * 判断对象是否存在
     */
    public boolean doesObjectExist(String bucketName, String objectName) {
        try {
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            return true; // 如果 stat 成功说明存在
        } catch (ErrorResponseException e) {
            if ("NoSuchKey".equals(e.errorResponse().code())) {
                return false;
            }
            throw new RuntimeException("检测对象是否存在时出错: " + e.getMessage(), e);
        } catch (Exception e) {
            throw new RuntimeException("检测对象是否存在时出错: " + e.getMessage(), e);
        }
    }


    /**
     * 使用 MinIO 的 ComposeObject 实现预签名分片合并（服务端直接合并，无需下载）
     *
     * @param reqVO 包含以下字段：
     *              - bucketName: 分片来源桶名称
     *              - fileId: 文件唯一 ID，用于生成分片路径
     *              - fileName: 合并后的文件名
     *              - partNames: 已上传的分片对象名列表
     * @return 合并后的文件访问 URL（预签名 GET URL）
     */
    public String mergeFilePartsByCompose(FileMergeReqVO reqVO) {
        String sourceBucket = reqVO.getBucketName();  // 分片来源桶
        String targetBucket = "merged";               // 合并目标桶
        String fileName = reqVO.getFileName();

        try {
            // 1️ 合并后的对象名（在 merged 桶根目录），URL 编码确保特殊字符不出错
            String mergedObjectName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());

            // 2️ 确保目标桶存在，如果不存在就创建
            boolean bucketExists = minioClient.bucketExists(
                    BucketExistsArgs.builder().bucket(targetBucket).build()
            );
            if (!bucketExists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(targetBucket).build());
                logger.info("目标桶不存在，已创建新桶: {}", targetBucket);
            }

            // 3️ 分片排序（按最后的索引数字升序）
            List<String> sortedParts = reqVO.getPartNames().stream()
                    .sorted(Comparator.comparingInt(name -> {
                        String idx = name.substring(name.lastIndexOf('-') + 1);
                        return Integer.parseInt(idx);
                    }))
                    .collect(Collectors.toList());
            logger.info("分片排序完成，共 {} 个分片: {}", sortedParts.size(), sortedParts);
            for (String part : sortedParts) {
                String objectName = URLDecoder.decode(part, String.valueOf(StandardCharsets.UTF_8));
                boolean exists = minioClient.statObject(StatObjectArgs.builder()
                        .bucket(sourceBucket)
                        .object(objectName)
                        .build()) != null;
                logger.info("检查分片: {} 是否存在 -> {}", objectName, exists);
            }

            // 4️ 构造 ComposeSource 列表，每个分片对应一个 ComposeSource
            List<ComposeSource> sources = sortedParts.stream()
                    .map(part -> ComposeSource.builder()
                            .bucket(sourceBucket)
                            .object(part)
                            .build())
                    .collect(Collectors.toList());

            // 5️ 执行跨桶合并（MinIO 服务端完成，不需要先下载）
            minioClient.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(targetBucket)
                            .object(mergedObjectName)
                            .sources(sources)
                            .build()
            );
            logger.info("分片合并完成: {} -> {}/{}", sourceBucket, targetBucket, mergedObjectName);

            // 6️ 异步下载合并后的文件到本地
            backupExecutor.submit(() -> {
                try {
                    String localDirPath = "/root/resources/" + sourceBucket;
                    File localDir = new File(localDirPath);
                    if (!localDir.exists()) localDir.mkdirs();

                    File localFile = new File(localDir, fileName);
                    minioClient.downloadObject(
                            DownloadObjectArgs.builder()
                                    .bucket(targetBucket)
                                    .object(mergedObjectName)
                                    .filename(localFile.getAbsolutePath())
                                    .build()
                    );
                    logger.info("异步保存到本地成功 -> {}", localFile.getAbsolutePath());
                } catch (Exception ex) {
                    logger.error("异步保存本地失败: {}", ex.getMessage(), ex);
                }
            });

            // 7️ 删除原桶中的分片，释放空间
            for (String part : sortedParts) {
                try {
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(sourceBucket)
                            .object(part)
                            .build());
                } catch (Exception e) {
                    logger.warn("删除分片失败: {}", part, e);
                }
            }

            // 8⃣ 在 Redis 中标记合并完成（7天有效期）
            redisTemplate.opsForValue().set("merged:" + reqVO.getFileId(), "done", 7, TimeUnit.DAYS);

            // 9️ 返回合并文件的访问 URL（预签名 GET URL）
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(targetBucket)
                            .object(mergedObjectName)
                            .build()
            );

        } catch (Exception e) {
            logger.error("MinIO 跨桶分片合并失败: {}", e.getMessage(), e);
            throw new RuntimeException("MinIO 分片合并失败: " + e.getMessage(), e);
        }
    }

    /**
     * 手动下载指定桶的所有文件到服务器目录 /root/resources/{bucketName}/
     *
     * @param bucketName 桶名称，若为空则默认 "satellite"
     * @return 下载结果说明
     */
    public boolean downloadToServer(String bucketName, String fileName) {
        try {
            // 本地保存路径
            String localDirPath = "/root/resources/" + bucketName + "/";

            logger.info("[MinIO-Service] 开始下载文件: bucket={}, fileName={}, 保存路径={}",
                    bucketName, fileName, localDirPath);

            File localDir = new File(localDirPath);
            if (!localDir.exists()) {
                localDir.mkdirs();
            }

            // 目标文件路径
            String localFilePath = localDirPath + fileName;

            // 从 MinIO 获取对象
            try (InputStream stream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(fileName)
                            .build())) {

                // 保存到本地
                Files.copy(stream, Paths.get(localFilePath), StandardCopyOption.REPLACE_EXISTING);
            }

            return true;
        } catch (Exception e) {
            logger.error("下载至服务器目录失败: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * Redis进度跟踪的文件夹上传
     */
    public Map<String, Object> uploadFolder(String bucketName, String folderPath, String taskId) {
        Map<String, Object> result = new HashMap<>();
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failedCount = new AtomicInteger(0);
        AtomicInteger totalFiles = new AtomicInteger(0);

        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(8);
        List<Future<?>> futures = new ArrayList<>();
        long startTime = System.currentTimeMillis();

        try {
            File folder = new File(folderPath);

            if (!folder.exists() || !folder.isDirectory()) {
                result.put("success", false);
                result.put("message", "文件夹不存在: " + folderPath);
                // 保存失败状态到Redis
                saveTaskStatus(taskId, "failed", result);
                return result;
            }

            if (!bucketExists(bucketName)) {
                makeBucket(bucketName);
            }

            String folderName = folder.getName();
            File[] files = folder.listFiles();

            if (files != null) {
                // 统计文件数量
                List<File> fileList = Arrays.stream(files)
                        .filter(File::isFile)
                        .collect(Collectors.toList());
                totalFiles.set(fileList.size());

                // 初始化任务状态
                Map<String, Object> initStatus = new HashMap<>();
                initStatus.put("status", "processing");
                initStatus.put("totalFiles", totalFiles.get());
                initStatus.put("successCount", 0);
                initStatus.put("failedCount", 0);
                initStatus.put("startTime", new Date().toString());
                initStatus.put("message", "开始上传...");
                saveTaskStatus(taskId, "processing", initStatus);

                logger.info("开始上传任务 {}: {} -> {}, 文件数量: {}",
                        taskId, folderPath, bucketName, totalFiles.get());

                // 提交所有上传任务
                for (File file : fileList) {
                    Future<?> future = executor.submit(() -> {
                        uploadSingleFile(file, bucketName, folderName, successCount, failedCount, taskId, totalFiles.get());
                    });
                    futures.add(future);
                }

                // 等待所有任务完成
                for (Future<?> future : futures) {
                    future.get();
                }
            }

            long endTime = System.currentTimeMillis();
            long totalTime = (endTime - startTime) / 1000;

            // 最终结果
            result.put("success", true);
            result.put("message", String.format("上传完成，成功: %d, 失败: %d, 耗时: %d秒",
                    successCount.get(), failedCount.get(), totalTime));
            result.put("successCount", successCount.get());
            result.put("failedCount", failedCount.get());
            result.put("totalTime", totalTime + "秒");

            // 保存完成状态到Redis
            saveTaskStatus(taskId, "completed", result);

        } catch (Exception e) {
            logger.error("文件夹上传失败: {}", e.getMessage(), e);
            result.put("success", false);
            result.put("message", "上传失败: " + e.getMessage());

            // 保存失败状态到Redis
            saveTaskStatus(taskId, "failed", result);
        } finally {
            executor.shutdown();
        }

        return result;
    }

    /**
     * 上传单个文件，并更新进度
     */
    private void uploadSingleFile(File file, String bucketName, String folderName,
                                  AtomicInteger successCount, AtomicInteger failedCount,
                                  String taskId, int totalFiles) {
        try {
            String objectName = folderName + "/" + file.getName();

            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(file.getAbsolutePath())
                            .build()
            );

            successCount.incrementAndGet();
            logger.info("文件上传成功: {} -> {}/{}", file.getName(), bucketName, objectName);

        } catch (Exception e) {
            failedCount.incrementAndGet();
            logger.error("文件上传失败: {} - {}", file.getName(), e.getMessage());
        } finally {
            // 每次上传完成后更新进度
            updateTaskProgress(taskId, successCount.get(), failedCount.get(), totalFiles);
        }
    }

    /**
     * 保存任务状态到Redis
     */
    private void saveTaskStatus(String taskId, String status, Map<String, Object> data) {
        try {
            Map<String, Object> taskStatus = new HashMap<>();
            taskStatus.put("taskId", taskId);
            taskStatus.put("status", status);
            taskStatus.put("updateTime", new Date().toString());
            taskStatus.putAll(data);

            // 保存到Redis，24小时过期
            redisTemplate.opsForValue().set(
                    "upload_task:" + taskId,
                    objectMapper.writeValueAsString(taskStatus),
                    24,
                    TimeUnit.HOURS
            );

            logger.debug("更新任务状态: {} -> {}", taskId, status);
        } catch (Exception e) {
            logger.error("保存任务状态到Redis失败: {}", e.getMessage(), e);
        }
    }

    /**
     * 更新任务进度
     */
    private void updateTaskProgress(String taskId, int successCount, int failedCount, int totalFiles) {
        try {
            Map<String, Object> progress = new HashMap<>();
            progress.put("taskId", taskId);
            progress.put("status", "processing");
            progress.put("successCount", successCount);
            progress.put("failedCount", failedCount);
            progress.put("totalFiles", totalFiles);
            progress.put("progress", String.format("%.1f%%", (successCount + failedCount) * 100.0 / totalFiles));
            progress.put("updateTime", new Date().toString());
            progress.put("message", String.format("处理中: %d/%d (成功: %d, 失败: %d)",
                    successCount + failedCount, totalFiles, successCount, failedCount));

            // 保存到Redis
            redisTemplate.opsForValue().set(
                    "upload_task:" + taskId,
                    objectMapper.writeValueAsString(progress),
                    24,
                    TimeUnit.HOURS
            );

        } catch (Exception e) {
            logger.error("更新任务进度失败: {}", e.getMessage(), e);
        }
    }

    /**
     * 获取任务状态
     */
    public Map<String, Object> getTaskStatus(String taskId) {
        try {
            String taskJson = redisTemplate.opsForValue().get("upload_task:" + taskId);
            if (taskJson != null) {
                return objectMapper.readValue(taskJson, new TypeReference<Map<String, Object>>() {});
            }
        } catch (Exception e) {
            logger.error("获取任务状态失败: {}", e.getMessage(), e);
        }

        // 默认返回
        Map<String, Object> defaultStatus = new HashMap<>();
        defaultStatus.put("taskId", taskId);
        defaultStatus.put("status", "not_found");
        defaultStatus.put("message", "任务不存在或已过期");
        return defaultStatus;
    }

}
